<!-- summary
  目标：通过打开和关闭两个方法，实现打开/关闭全局弹窗，同时让弹窗属性统一，开发时无需关心属性设置，但支持属性值覆盖。
  目前只提供了两种弹出层：dialog 和 drawer，默认为 dialog。
-->
<template>
  <slot />
  <!-- 对话框 -->
  <BasicDialog v-model="dialog.show" v-bind="dialog.attrs" v-for="(dialog, ind) in dialogs" :key="'dialog-' + ind">
    <BaseRender :renderData="dialog.body" />
  </BasicDialog>
  <!-- 抽屉 -->
  <BasicDrawer v-model="drawer.show" v-bind="drawer.attrs" v-for="(drawer, ind) in drawers" :key="'drawer-' + ind">
    <BaseRender :renderData="drawer.body" />
  </BasicDrawer>
</template>
<script lang="ts" setup>
import { reactive, shallowReactive, provide, toRaw } from "vue";
import { isRenderData, showMessage, sortObjArrByKey, typeOf } from "@/utils";
import { defaultPopupType, defaultDialogAttrs, defaultDrawerAttrs } from "@/core/config";
import { popupCloseAnimationDuration } from "@/core/consts";
import type { CommonObj, SetTimeout } from "@/core/_types";
//不取名为BaseDialog和BaseDrawer的原因是，这两个名字会被自动注册为全局组件，但是却用的很少，影响一定的性能，但又是极低频率会导入引用的组件，所以以Basic开头
import BasicDialog from "./_components/BasicDialog.vue";
import BasicDrawer from "./_components/BasicDrawer.vue";
import type { BaseRenderData } from "@/core/components/BaseRender.vue";
import type {
  PopupType,
  DialogId,
  DrawerId,
  FootRenderData,
  DialogPopup,
  DrawerPopup,
  ClosePopupType,
  CloseDialogType,
  CloseDrawerType,
  DialogHeadTypes,
  DrawerHeadTypes,
} from "./_types";

let dialogTimer: SetTimeout = null;
let drawerTimer: SetTimeout = null;

const closeDelay = popupCloseAnimationDuration; //延迟一点置为空（为了配合动画效果）
const dialogs = shallowReactive<DialogPopup[]>([]);
const drawers = shallowReactive<DrawerPopup[]>([]);

/**
 * 判断是否是弹窗对象
 * @tips 用作排除null、事件对象
 */
function getIsPopupObj(popup: CommonObj) {
  if (Object.keys(popup).length > 7) return false;
  return "id" in popup && "name" in popup && "show" in popup && "attrs" in popup && "body" in popup && "createAt" in popup;
}

/**
 * 获取弹出层的属性
 */
function getPopupAttrs(head: any, popupId: DrawerId | DialogId, defAttrs?: CommonObj): any {
  if (isRenderData(head)) return { ...defAttrs, title: head, onClose: () => closePopup(popupId) };
  return { ...defAttrs, ...head };
}

/**
 * 对话框 dialog
 */
function openDialog(head: DialogHeadTypes | DialogId, body?: BaseRenderData) {
  // if (dialogTimer) return showMessage("弹窗操作太频繁了", "warning");
  if (dialogTimer) closeDialog(undefined, true); // 为了处理关闭弹窗，然后马上打开弹窗会提示操作频繁的问题
  const t = typeOf(head);
  if (t === "String") {
    if ((head as string).startsWith("dialog-") && !body) {
      const id = Number((head as string).split("-")[1]);
      const target = dialogs.find(it => it.id === id);
      if (target) return (target.show = true);
      return showMessage(`不存在对话框【dialog-${id}】`, "error");
    }
  }
  const id = (dialogs.at(-1)?.id ?? -1) + 1;
  dialogs.push(
    shallowReactive<DialogPopup>({
      id,
      name: "dialog",
      show: true,
      attrs: getPopupAttrs(head, `dialog-${id}`, defaultDialogAttrs),
      body,
      createAt: Date.now(),
    })
  );
}

function closeDialog(popup: CloseDialogType = `dialog-${dialogs.at(-1)?.id ?? 0}`, immediate = false) {
  if (popup === "all") {
    dialogs.length = 0;
    return;
  }
  const ind = dialogs.findIndex(item => {
    if (typeof popup !== "string") return item === popup;
    return item.id === Number(popup.split("-")[1]);
  });
  if (ind === -1) return;
  dialogs[ind].show = false;
  const { destroyOnClose } = dialogs[ind].attrs;
  if (!destroyOnClose) return;
  if (!immediate) {
    dialogTimer = setTimeout(() => {
      dialogs.splice(ind, 1);
      dialogTimer = null;
    }, closeDelay);
    return;
  }
  dialogs.splice(ind, 1);
  if (dialogTimer) {
    clearTimeout(dialogTimer);
    dialogTimer = null;
  }
}

/**
 * 抽屉 drawer
 */
function openDrawer(head: DrawerHeadTypes | DrawerId, body?: BaseRenderData) {
  // if (drawerTimer) return showMessage("弹窗操作太频繁了", "warning");
  if (drawerTimer) closeDrawer(undefined, true); // 为了处理关闭弹窗，然后马上打开弹窗会提示操作频繁的问题
  const t = typeOf(head);
  if (t === "String") {
    if ((head as string).startsWith("drawer-") && !body) {
      const id = Number((head as string).split("-")[1]);
      const target = drawers.find(it => it.id === id);
      if (target) return (target.show = true);
      return showMessage(`不存在抽屉【drawer-${id}】`, "error");
    }
  }
  const id = (drawers.at(-1)?.id ?? -1) + 1;
  drawers.push(
    shallowReactive<DrawerPopup>({
      id,
      name: "drawer",
      show: true,
      attrs: getPopupAttrs(head, `drawer-${id}`, defaultDrawerAttrs),
      body,
      createAt: Date.now(),
    })
  );
}

function closeDrawer(popup: CloseDrawerType = `drawer-${drawers.at(-1)?.id ?? 0}`, immediate = false) {
  if (popup === "all") {
    drawers.length = 0;
    return;
  }
  const ind = drawers.findIndex(item => {
    if (typeof popup !== "string") return item === popup;
    return item.id === Number(popup.split("-")[1]);
  });
  if (ind === -1) return;
  drawers[ind].show = false;
  const { destroyOnClose } = drawers[ind].attrs;
  if (!destroyOnClose) return;
  if (!immediate) {
    drawerTimer = setTimeout(() => {
      drawers.splice(ind, 1);
      drawerTimer = null;
    }, closeDelay);
    return;
  }
  drawers.splice(ind, 1);
  if (drawerTimer) {
    clearTimeout(drawerTimer);
    drawerTimer = null;
  }
}

/**
 * 混合弹出层 popup
 */
function openPopup(
  head: DrawerHeadTypes | DialogHeadTypes | DialogId | DrawerId,
  body?: BaseRenderData,
  type: PopupType | FootRenderData = defaultPopupType
) {
  if (typeof head === "string") {
    const isDiaId = head.startsWith("dialog-") && !body;
    if (isDiaId) return openDialog(head);
    const isDraId = head.startsWith("drawer-") && !body;
    if (isDraId) return openDrawer(head);
  }
  if (type === "dialog") return openDialog(head, body);
  if (type === "drawer") return openDrawer(head, body);
  throw new Error(`暂不支持此类型弹窗：${type}`);
}

/**
 * 获取顶层弹窗的id数组
 */
function getTopPopupIds(num = 1): (DrawerId | DialogId)[] {
  if (num <= 0) throw new Error("请传入一个正整数");
  const popups = sortObjArrByKey([...dialogs, ...drawers], "desc", "createAt");
  const topPops = popups.slice(0, num);
  return topPops?.map(it => `${it.name}-${it.id}` as DrawerId | DialogId);
}
function closePopup(popup: ClosePopupType = 1, immediate = false): void {
  if (popup === "all") {
    closeDialog(popup, immediate);
    closeDrawer(popup, immediate);
    return;
  }
  const t = typeOf(popup);
  if (t === "String") {
    const isId = (popup as string).includes("-");
    if ((popup as string).startsWith("dialog")) {
      const isAll = popup === "dialog";
      const dia = isId ? (popup as CloseDialogType) : isAll ? "all" : undefined;
      return closeDialog(dia, immediate);
    }
    if ((popup as string).startsWith("drawer")) {
      const isAll = popup === "drawer";
      const dra = isId ? (popup as CloseDrawerType) : isAll ? "all" : undefined;
      return closeDrawer(dra, immediate);
    }
    throw new Error(`暂未处理此种弹窗类型：${popup}`);
  }
  if (t === "Number") {
    const ids = getTopPopupIds(popup as number);
    ids.forEach((id: DrawerId | DialogId) => closePopup(id));
    return;
  }
  if (popup === null || t === "PointerEvent") return closePopup();
  // 排除null、事件对象
  if (t === "Object") {
    if (!getIsPopupObj(popup as  DialogPopup | DrawerPopup)) return closePopup();
    const isDialog = (popup as DialogPopup | DrawerPopup).name === "dialog";
    return isDialog ? closeDialog(popup as DialogPopup, immediate) : closeDrawer(popup as DrawerPopup, immediate);
  }
  throw new Error(`暂未处理此类型：${t}`);
}

/**
 * 获取弹出层栈
 */
function getPopups(type?: PopupType) {
  if (type === "dialog") return toRaw(dialogs);
  if (type === "drawer") return toRaw(drawers);
  return { dialogs: toRaw(dialogs), drawers: toRaw(drawers) };
}

//provide提供给子组件使用
provide("openPopup", openPopup);
provide("closePopup", closePopup);
provide("getPopups", getPopups);
</script>
<style lang="scss" scoped></style>
